package core.lexical;

import java.io.FileReader;
import java.io.IOException;

import core.Token;
import core.error.NoMoreTokensException;
import core.error.SyntaxException;

/**
 * It splits the text in the source code into {@link Token} objects.
 * The last {@link Token} returned is the "End Of File" {@link Token}. An runtime
 * exception is launched if another {@link Token} is asked for.
 */
public class LexicalTokenizer {

	/**
	 * The pipe operator
	 */
	private static final String PIPE_OP = "|";
	
	/**
	 * The equal operator
	 */
	private static final String EQUALS_OP = "=";
	
	/**
	 * The string which represents the relational operator(!) and half of logical operator(!=). 
	 */
	private static final String STRING = "!";
	
	/**
	 * Less than operator
	 */
	private static final String LESS_THAN_OP = "<";
	
	/**
	 * Greater than operator
	 */
	private static final String GREATER_THAN_OP = ">";
	
	/**
	 * End of file code
	 */
	private static final int EOF_CHAR = -1;
	
	/**
	 * Line separator
	 */
	private static final String LINE_SEPARATOR = System.getProperty("line.separator");

	
	/**
	 * The ASCII representation of the next character.
	 */
	private static int nextCharacter;
	
	/**
	 * The empty character representation in the ASCII table.
	 */
	private static final int EMPTY_CHAR = 32;
	
	/**
	 * 
	 */
	private static StringBuffer sb;
	
	/**
	 * The file reader.
	 */
	private final FileReader sourceReader;
	
	/**
	 * 
	 */
	private boolean end = false;
	
	private int lineNumber;

	/**
	 * Constructs the LexicalTokenizer using the given <code>FileReader</code>.
	 * @param sourceReader The file reader.
	 */
	public LexicalTokenizer( FileReader sourceReader ) {
		this.sourceReader = sourceReader;
		nextCharacter = EMPTY_CHAR;
		lineNumber = 1;
	}

	/**
	 * Returns the next token from the source code.
	 * @return Token - the next token from the source code.
	 */
	public Token nextToken(){

		if ( isEOF( nextCharacter ) ){
			if (!end){
				end = true;
				return new Token( "^D" );
			}
			throw new NoMoreTokensException("The end of file signal has already been reached.");
		}

		try{
			
			while( isWhitespace( (char) nextCharacter ) ){
				nextCharacter = sourceReader.read(); 
			}


			sb = new StringBuffer( 2 );

			if ( Character.isDigit( (char) nextCharacter) ){
				while ( Character.isDigit( (char)nextCharacter ) ) {
					sb.append( (char)nextCharacter );
					nextCharacter = sourceReader.read();
				}
				return new Token( sb.toString().trim() );
			}

			if (Character.isLetter( (char)nextCharacter )){
				while ( Character.isLetterOrDigit( (char) nextCharacter ) ) {
					sb.append( (char)nextCharacter );
					nextCharacter = sourceReader.read();
				}
				return new Token( sb.toString().trim() );
			}
			
			//char
			if ('\'' == (char)nextCharacter ){
				sb.append( (char)nextCharacter );
				nextCharacter = sourceReader.read();
				if('\'' == (char)nextCharacter){
					throw new SyntaxException( "Invalid character constant");
				}
				
				sb.append( (char)nextCharacter );
				nextCharacter = sourceReader.read();
				if('\'' != (char)nextCharacter){
					throw new SyntaxException( "Invalid character constant");
				}
				sb.append( (char)nextCharacter );
				nextCharacter = sourceReader.read();
				return new Token( sb.toString() );
			}


			if ('\"' == (char)nextCharacter ){
				sb.append( (char)nextCharacter );
				nextCharacter = sourceReader.read();
				while((char)nextCharacter != '"'){
					
					//Condi��o para tratamento de string sem fecha-aspas;
					if(nextCharacter ==  EOF_CHAR){
						throw new SyntaxException( "String literal is not properly closed by a double-quote.");
					}
					
					sb.append( (char)nextCharacter );
					nextCharacter = sourceReader.read();
					if ((char)nextCharacter == '\\'){
						nextCharacter = sourceReader.read();
						sb.append( (char)nextCharacter );
						nextCharacter = sourceReader.read();
					}
				}
				sb.append( (char)nextCharacter );
				nextCharacter = sourceReader.read();
				return new Token( sb.toString() );
			}

			sb.append( (char)nextCharacter );
			nextCharacter = sourceReader.read();

			if (isCompositeOperand( sb.toString(), String.valueOf( (char)nextCharacter ) ) ){
				sb.append( (char)nextCharacter );
				nextCharacter = sourceReader.read();
				return new Token( sb.toString().trim() );
			}


			if (sb.toString().equals("/") && (char)nextCharacter == '*' ){
				nextCharacter = sourceReader.read();
				boolean endComments = false;
				while( ! endComments ){
					int temp = nextCharacter;
					
					//	Condi��o para tratamento de coment�rio sem */
					if(nextCharacter ==  EOF_CHAR){
						throw new SyntaxException( "Comment is not properly closed.");
					}
					
					nextCharacter = sourceReader.read();
					endComments = ( temp == '*' && nextCharacter == '/');
				}
				nextCharacter = sourceReader.read();
				return this.nextToken();
			}
			return new Token( sb.toString() );
		} catch ( IOException exception ) {
			throw new RuntimeException();
		}

	}

	private boolean isWhitespace(char nextCharacter2) {
//		String charStr = nextCharacter2 + "";
		if(nextCharacter2 == 10){
			lineNumber++;
		}
		return Character.isWhitespace(nextCharacter2);
	}

	/**
	 * Verifying if the operator is composite
	 * @param firstOperand - the first operator.
	 * @param secondOperand - the second operator.
	 * @return true if the operator is composite, otherwise false.
	 */
	private static boolean isCompositeOperand( String firstOperand, String secondOperand ) {
		if ( GREATER_THAN_OP.equals( firstOperand ) || LESS_THAN_OP.equals( firstOperand )
				|| STRING.equals( firstOperand ) || EQUALS_OP.equals( firstOperand ) )
			return secondOperand.equals( EQUALS_OP );
		return PIPE_OP.equals( firstOperand ) && PIPE_OP.equals( secondOperand );
	}

	/**
	 * Verifying if the end of file happened. 
	 * @param character - the character to be checked.
	 * @return true if character represents the end of file, otherwise false. 
	 */
	private static boolean isEOF( int character ) {
		return character == EOF_CHAR;
	}

	public int getLineNumber() {
		return lineNumber;
	}
	
	
}